package ru.volgogradetzzz.display {
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DRenderMode;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.display3D.VertexBuffer3D;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;
	import ru.volgogradetzzz.math.VMatrix4x4;
	
	/**
	 * ...
	 * @author Volgogradetzzz
	 */
	public class Main3D extends Sprite {
		
		private const SCALE_TRANSFORM:Matrix = new Matrix();
		
		protected var _stage3D:Stage3D;
        protected var _context3D:Context3D;
        protected var _program:Program3D;
		
		private var _w:Number = 800;
		private var _h:Number = 600;
		
		public function Main3D() {
			if (stage) {
				init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}
		
		protected function start():void {
			addEventListener(Event.ENTER_FRAME, render);
		}
		
		protected function stop():void {
			removeEventListener(Event.ENTER_FRAME, render);
		}
		
		protected function init(event:Event = null):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
			
            prepareStage3D();
        }
		
		protected function prepareStage3D():void {
            _stage3D = stage.stage3Ds[0];
            _stage3D.addEventListener(Event.CONTEXT3D_CREATE, contextCreatedHandler);
			_stage3D.addEventListener(ErrorEvent.ERROR, errorHandler);
            _stage3D.requestContext3D(Context3DRenderMode.AUTO);
			
			stageResizeHandler();
        }
		
		protected function contextCreatedHandler(event:Event):void {
			_context3D =  _stage3D.context3D;
            if (!_context3D) {
                return;
            }
			
            _context3D.configureBackBuffer(_w, _h, 4, true);
            _context3D.setCulling(Context3DTriangleFace.BACK);
			_context3D.enableErrorChecking = true;
			_context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
			
			_program = _context3D.createProgram();
            
			stage.addEventListener(Event.RESIZE, stageResizeHandler);
			
			main();
		}
		
		protected function stageResizeHandler(event:Event = null):void {
			if(_stage3D != null) {
				_stage3D.x = (stage.stageWidth - _w) * 0.5;
				_stage3D.y = (stage.stageHeight - _h) * 0.5;
			}
		}
		
		protected function enableErrorChecking(value:Boolean):void {
			_context3D.enableErrorChecking = value;
		}
		
		protected function configureBackBuffer(w:int, h:int, antiAlias:int, enableDepthAndStencil:Boolean = true):void {
			_w = w;
			_h = h;
			_context3D.configureBackBuffer(_w, _h, antiAlias, enableDepthAndStencil);
			
			stageResizeHandler();
		}
		
		protected function setScissorRectangle(rect:Rectangle):void {
			_context3D.setScissorRectangle(rect);
		}
		
		protected function errorHandler(event:ErrorEvent):void {
			trace('requested rendering context cannot be created');
		}
		
		protected function main():void {
            
        }
		
		protected function draw():void {
			
        }
		
		protected function setCulling(triangleFaceToCull:String):void {
			_context3D.setCulling(triangleFaceToCull);
		}
		
		protected function setBlendFactors(sourceFactor:String, destinationFactor:String):void {
			_context3D.setBlendFactors(sourceFactor, destinationFactor);
		}
		
		protected function setDepthTest(depthMask:Boolean, passCompareMode:String):void {
			_context3D.setDepthTest(depthMask, passCompareMode);
		}
		
		protected function drawTriangles(indexBuffer:IndexBuffer3D, firstIndex:uint = 0, numTriangles:int = -1):void {
			_context3D.drawTriangles(indexBuffer, firstIndex, numTriangles);
		}
		
		protected function setVertexBuffer(id:uint, vertexBuffer:VertexBuffer3D, format:String):void {
			_context3D.setVertexBufferAt(id, vertexBuffer, 0, format);
		}
		
		protected function setRenderToTexture(texture:Texture):void {
			_context3D.setRenderToTexture(texture);
		}
		
		protected function setRenderToBackBuffer():void {
			_context3D.setRenderToBackBuffer();
		}
		
		protected function clear(r:Number = 0, g:Number = 0, b:Number = 0, a:Number = 1, d:Number = 1, s:uint = 0):void {
			_context3D.clear(r, g, b, a, d, s);
		}
		
		protected function render(event:Event=null):void {
            draw();
            _context3D.present();
        }
		
		protected function setShaders(vertexShader:ByteArray, fragmentShader:ByteArray):void {
			_program.upload(vertexShader, fragmentShader);
			_context3D.setProgram(_program);
		}
		
		protected function setVertexData(vertexData:Vector.<Number>, dataPerVertex:uint):VertexBuffer3D {
			var numVertices:uint = vertexData.length / dataPerVertex;
            var vertexBuffer:VertexBuffer3D = _context3D.createVertexBuffer(numVertices, dataPerVertex);
            vertexBuffer.uploadFromVector(vertexData, 0, numVertices);
			
			return vertexBuffer;
        }
		
		protected function setIndexData(indexData:Vector.<uint>):IndexBuffer3D {
            var indexBuffer:IndexBuffer3D = _context3D.createIndexBuffer(indexData.length);
            indexBuffer.uploadFromVector(indexData, 0, indexData.length);
			
			return indexBuffer;
        }
		
		protected function setTextureData(bd:BitmapData, useMipMapping:Boolean = false):Texture {
            var texture:Texture = _context3D.createTexture(bd.width, bd.height, Context3DTextureFormat.BGRA, false);
			
			if (useMipMapping) {
				var mipWidth:int = bd.width;
				var mipHeight:int = bd.height;
				var mipLevel:int = 0;
				var mipImage:BitmapData = new BitmapData(bd.width, bd.height, true, 0x00000000);
				
				SCALE_TRANSFORM.identity();
				
				while (mipWidth > 0 || mipHeight > 0) {
					mipImage.fillRect(mipImage.rect, 0x00000000);
					mipImage.draw(bd, SCALE_TRANSFORM, null, null, null, true);
					texture.uploadFromBitmapData(mipImage, mipLevel);
					SCALE_TRANSFORM.scale(0.5, 0.5);
					mipLevel++;
					mipWidth >>= 1;
					mipHeight >>= 1;
				}
				mipImage.dispose();
			}else{
				texture.uploadFromBitmapData(bd);
			}
			
			return texture;
        }
		
		protected function createTextureToRender(w:Number, h:Number):Texture {
			return _context3D.createTexture(w, h, Context3DTextureFormat.BGRA, true);
		}
		
		protected function setTextureAt(id:uint, texture:Texture):void {
			_context3D.setTextureAt(id, texture);
		}
		
		protected function setVertexVectorConstant(id:uint, vec:Vector.<Number>):void {
			_context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, id, vec);
		}
		
		protected function setFragmentVectorConstant(id:uint, vec:Vector.<Number>):void {
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, id, vec);
		}
		
		protected function setVertexMatrix(startIndex:uint, m:VMatrix4x4):void {
			setVertexVectorConstant(startIndex, 	Vector.<Number>([m.m11, m.m21, m.m31, m.m41]));
			setVertexVectorConstant(startIndex + 1, Vector.<Number>([m.m12, m.m22, m.m32, m.m42]));
			setVertexVectorConstant(startIndex + 2, Vector.<Number>([m.m13, m.m23, m.m33, m.m43]));
			setVertexVectorConstant(startIndex + 3, Vector.<Number>([m.m14, m.m24, m.m34, m.m44]));
		}
	}
}